using System;
using System.Collections.Generic;
using System.Text;
using NBusiness.CodeDom.Compiler;
using NBusiness.ESharp.Compiler;
using NBusiness.CodeDom;
using System.CodeDom;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using Microsoft.VisualBasic;
using NBusiness.Templates.Contracts;
using NBusiness.Data;
using NBusiness.Utilities;
using NBusiness.Query;

namespace NBusiness.Templates
{
    public class WebServiceTemplate : TemplateBase
    {
        #region Generate
        public override EntityResourceFile[] Generate(PartialLanguage partialLanguage, params Entity[] entities)
        {
            List<EntityResourceFile> files = new List<EntityResourceFile>();

            //Group entities by family. A service will be created for each family group.
            Dictionary<string, List<Entity>> entityGroups = new Dictionary<string, List<Entity>>();
            foreach (Entity entity in entities)
            {
                string family = (entity.Parent as EntityFamily).Fullname;
                if (!entityGroups.ContainsKey(family))
                    entityGroups.Add(family, new List<Entity>());
                
                entityGroups[family].Add(entity);
            }

            foreach (string family in entityGroups.Keys)
            {
                //Generates one partial file with class level attributes, then N partial files with webmethods.
                CodeCompileUnit unit = new CodeCompileUnit();
                CodeNamespace n = new CodeNamespace(family);
                string fileName = "WebService\\" + family + "Service." + partialLanguage.ToString().ToLower();

                n.Comments.Add(new CodeCommentStatement(fileName + " - '" + this.GetType().FullName + "'"));
                n.Comments.Add(new CodeCommentStatement("This file was automatically generated by NBusiness"));
                n.Comments.Add(new CodeCommentStatement("on " + DateTime.Now.ToShortDateString() + " at " + DateTime.Now.ToLongTimeString()));

                n.Imports.Add(new CodeNamespaceImport("System"));
                n.Imports.Add(new CodeNamespaceImport("System.Web"));
                n.Imports.Add(new CodeNamespaceImport("System.Collections"));
                n.Imports.Add(new CodeNamespaceImport("System.Web.Services"));
                n.Imports.Add(new CodeNamespaceImport("System.Web.Services.Protocols"));
                n.Imports.Add(new CodeNamespaceImport("System.Web.Security"));
                unit.Namespaces.Add(n);

                CodeTypeDeclaration masterDeclaration = CreateMasterDeclaration(family);
                n.Types.Add(masterDeclaration);
                EntityResourceFile generatedFile = GenerateFile(partialLanguage, unit, fileName);
                files.Add(generatedFile);

                foreach (Entity entity in entityGroups[family])
                {
                    n.Types.Clear();
                    fileName = "WebService\\" + entity.Fullname + "." + partialLanguage.ToString().ToLower();

                    CodeTypeDeclaration d = CreateMainClass(entity);
                    if (d != null)
                    {
                        n.Types.Add(d);

                        CodeMemberMethod[] fetches = GetCollectionFetchMethods(entity);
                        CodeMemberMethod[] crud = GetCrudMethods(entity);

                        d.Members.AddRange(fetches);
                        d.Members.AddRange(crud);

                        generatedFile = GenerateFile(partialLanguage, unit, fileName);
                        files.Add(generatedFile);
                    }
                }
            }
            return files.ToArray();
        }
        #endregion

        #region Create Fetches
        private CodeMemberMethod[] GetCollectionFetchMethods(Entity entity)
        {
            List<CodeMemberMethod> methods = new List<CodeMemberMethod>();
            methods.AddRange(CreateFetchAlls(entity));
            methods.AddRange(CreateFetchRelationship(entity));

            methods[0].StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, "Collection Fetch Methods"));
            methods[methods.Count - 1].EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            return methods.ToArray();
        }
        private CodeMemberMethod[] CreateFetchAlls(Entity entity)
        {
            List<CodeMemberMethod> methods = new List<CodeMemberMethod>();
            bool useInfo = (Dependency.GetContract<IInfoObject>(entity) != null);
            
            CodeMemberMethod fetchAllMethod1 = new CodeMemberMethod();
            methods.Add(fetchAllMethod1);
            fetchAllMethod1.Attributes = MemberAttributes.Public;
            fetchAllMethod1.Name = entity.Name + "CollectionFetchAll";
            fetchAllMethod1.CustomAttributes.Add(new CodeAttributeDeclaration("WebMethod"));
            fetchAllMethod1.ReturnType = (useInfo ?
                new CodeTypeReference(entity.Name + "Info[]") :
                new CodeTypeReference(entity.Name + "[]"));
            
            CodeTypeReference collectionType = new CodeTypeReference(entity.Name + "Collection");
            CodeTypeReferenceExpression collectionTypeReference = new CodeTypeReferenceExpression(entity.Name + "Collection");
            CodeMethodInvokeExpression fetchAll = new CodeMethodInvokeExpression(
                collectionTypeReference,
                "FetchAll");
            CodeVariableDeclarationStatement entityCollection = new CodeVariableDeclarationStatement(
                collectionType,
                "collection",
                fetchAll);
            CodeVariableReferenceExpression collection = new CodeVariableReferenceExpression(
                "collection");
            CodeMethodReturnStatement returnCollection = new CodeMethodReturnStatement(
                (useInfo ?
                (CodeExpression)new CodeMethodInvokeExpression(collection, "ToInfoArray") :
                (CodeExpression)collection));

            fetchAllMethod1.Statements.Add(entityCollection);
            fetchAllMethod1.Statements.Add(returnCollection);

            CodeMemberMethod fetchAllMethod2 = new CodeMemberMethod();
            methods.Add(fetchAllMethod2);
            fetchAllMethod2.Attributes = MemberAttributes.Public;
            fetchAllMethod2.Name = entity.Name + "CollectionFetchAllPagedSorted";
            fetchAllMethod2.CustomAttributes.Add(new CodeAttributeDeclaration("WebMethod"));

            fetchAllMethod2.ReturnType = (useInfo ?
                new CodeTypeReference(entity.Name + "Info[]") :
                new CodeTypeReference(entity.Name + "[]"));

            fetchAllMethod2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "startIndex"));
            fetchAllMethod2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "maxRows"));
            fetchAllMethod2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "sortField"));
            fetchAllMethod2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(SortOrder), "order"));
            
            CodeParameterDeclarationExpression totalEntitiesParameter = new CodeParameterDeclarationExpression(typeof(int), "totalEntities");
            CodeVariableReferenceExpression totalEntitiesReference = new CodeVariableReferenceExpression("totalEntities");
            totalEntitiesParameter.Direction = FieldDirection.Out;
            fetchAllMethod2.Parameters.Add(totalEntitiesParameter);

            CodeVariableDeclarationStatement totalDeclaration = new CodeVariableDeclarationStatement(typeof(int), "total");
            CodeVariableReferenceExpression totalReference = new CodeVariableReferenceExpression("total");

            CodeDirectionExpression totalDirection = new CodeDirectionExpression(
                FieldDirection.Out, totalReference);

            fetchAll = new CodeMethodInvokeExpression(
                collectionTypeReference,
                "FetchAll",
                new CodeVariableReferenceExpression("startIndex"),
                new CodeVariableReferenceExpression("maxRows"),
                new CodeVariableReferenceExpression("sortField"),
                new CodeVariableReferenceExpression("order"),
                totalDirection);

            entityCollection = new CodeVariableDeclarationStatement(
                collectionType,
                "collection",
                fetchAll);

            CodeAssignStatement assignTotal = new CodeAssignStatement(
                totalEntitiesReference,
                totalReference);

            returnCollection = new CodeMethodReturnStatement(
                (useInfo ?
                (CodeExpression)new CodeMethodInvokeExpression(collection, "ToInfoArray") :
                (CodeExpression)collection));
            
            fetchAllMethod2.Statements.Add(totalDeclaration);
            fetchAllMethod2.Statements.Add(entityCollection);
            fetchAllMethod2.Statements.Add(assignTotal);
            fetchAllMethod2.Statements.Add(returnCollection);

            return methods.ToArray();
        }
        private CodeMemberMethod[] CreateFetchRelationship(Entity entity)
        {
            List<CodeMemberMethod> methods = new List<CodeMemberMethod>();

            foreach (EntityRelationship relationship in entity.Relationships)
            {
                if (relationship.From == entity && relationship.Type == EntityRelationshipType.Parent)
                {
                    CodeMemberMethod[] fetchByParentMethods = CreateFetchByParent(relationship);
                    methods.AddRange(fetchByParentMethods);
                }
                //else if (relationship.From == entity && relationship.Type == EntityRelationshipType.Sibling)
                //{
                //    CodeMemberMethod[] fetchBySiblingMethods = CreateFetchBySiblings(relationship);
                //    methods.AddRange(fetchBySiblingMethods);
                //}
            }

            return methods.ToArray();
        }
        private CodeMemberMethod[] CreateFetchByParent(EntityRelationship relationship)
        {
            List<CodeMemberMethod> methods = new List<CodeMemberMethod>();
            bool useInfo = (Dependency.GetContract<IInfoObject>(relationship.From) != null);

            #region Create fetchByRelationship1
            CodeMemberMethod fetchByRelationship1 = new CodeMemberMethod();
            methods.Add(fetchByRelationship1);
            fetchByRelationship1.Attributes = MemberAttributes.Public;
            fetchByRelationship1.Name = relationship.From.Name + "CollectionFetchBy" + relationship.With.Name;
            fetchByRelationship1.ReturnType = (useInfo ?
                new CodeTypeReference(relationship.From.Name + "Info[]") :
                new CodeTypeReference(relationship.From.Name + "Info[]"));

            CodeAttributeDeclaration webMethodAttributeDeclaration = new CodeAttributeDeclaration("WebMethod");
            fetchByRelationship1.CustomAttributes.Add(webMethodAttributeDeclaration);

            CodeTypeReference collectionTypeReference1 = new CodeTypeReference(relationship.From.Name + "Collection");
            CodeTypeReferenceExpression collectionTypeReferenceExpression1 = new CodeTypeReferenceExpression(relationship.From.Name + "Collection");
            CodeMethodInvokeExpression fetchByInvoke1 = new CodeMethodInvokeExpression(
                collectionTypeReferenceExpression1,
                "FetchBy" + relationship.With.Name);

            CodeVariableDeclarationStatement collectionDeclaration1 = new CodeVariableDeclarationStatement(
                collectionTypeReference1,
                "collection",
                fetchByInvoke1);
            CodeVariableReferenceExpression collectionReference1 = new CodeVariableReferenceExpression("collection");

            CodeMethodReturnStatement returnStatement1 = new CodeMethodReturnStatement(
                (useInfo ?
                (CodeExpression)new CodeMethodInvokeExpression(collectionReference1, "ToInfoArray") :
                (CodeExpression)collectionReference1));

            foreach (EntityField field in relationship.With.Fields)
            {
                if (field.IsId)
                {
                    fetchByRelationship1.Parameters.Add(new CodeParameterDeclarationExpression(
                        new CodeTypeReference(field.Type),
                        StringUtil.ToCamelCase(field.Name)));

                    fetchByInvoke1.Parameters.Add(new CodeVariableReferenceExpression(StringUtil.ToCamelCase(field.Name)));
                }
            }

            fetchByRelationship1.Statements.Add(collectionDeclaration1);
            fetchByRelationship1.Statements.Add(returnStatement1);
            #endregion

            #region Create fetchByRelationship2
            CodeMemberMethod fetchByRelationship2 = new CodeMemberMethod();
            methods.Add(fetchByRelationship2);
            fetchByRelationship2.Attributes = MemberAttributes.Public;
            fetchByRelationship2.Name = relationship.From.Name + "CollectionFetchBy" + relationship.With.Name + "PagedSorted";
            fetchByRelationship2.ReturnType = (useInfo ?
                new CodeTypeReference(relationship.From.Name + "Info[]") :
                new CodeTypeReference(relationship.From.Name + "[]"));
            fetchByRelationship2.CustomAttributes.Add(webMethodAttributeDeclaration);

            CodeTypeReference collectionTypeReference2 = new CodeTypeReference(relationship.From.Name + "Collection");
            CodeTypeReferenceExpression collectionTypeReferenceExpression2 = new CodeTypeReferenceExpression(relationship.From.Name + "Collection");
            CodeMethodInvokeExpression fetchByInvoke2 = new CodeMethodInvokeExpression(
                collectionTypeReferenceExpression2,
                "FetchBy" + relationship.With.Name);

            CodeVariableDeclarationStatement collectionDeclaration2 = new CodeVariableDeclarationStatement(
                collectionTypeReference2,
                "collection",
                fetchByInvoke2);
            CodeVariableReferenceExpression collectionReference2 = new CodeVariableReferenceExpression("collection");

            foreach (EntityField field in relationship.With.Fields)
            {
                if (field.IsId)
                {
                    fetchByRelationship2.Parameters.Add(new CodeParameterDeclarationExpression(
                        new CodeTypeReference(field.Type),
                        StringUtil.ToCamelCase(field.Name)));

                    fetchByInvoke2.Parameters.Add(new CodeVariableReferenceExpression(StringUtil.ToCamelCase(field.Name)));
                }
            }

            CodeVariableDeclarationStatement totalDeclaration = new CodeVariableDeclarationStatement(typeof(int), "total");
            CodeVariableReferenceExpression totalReference = new CodeVariableReferenceExpression("total");

            fetchByRelationship2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "startIndex"));
            fetchByRelationship2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "maxRows"));
            fetchByRelationship2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "sortField"));
            fetchByRelationship2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(SortOrder), "order"));
            CodeParameterDeclarationExpression totalParameter = new CodeParameterDeclarationExpression(typeof(int), "totalEntities");
            totalParameter.Direction = FieldDirection.Out;
            fetchByRelationship2.Parameters.Add(totalParameter);

            fetchByInvoke2.Parameters.Add(new CodeVariableReferenceExpression("startIndex"));
            fetchByInvoke2.Parameters.Add(new CodeVariableReferenceExpression("maxRows"));
            fetchByInvoke2.Parameters.Add(new CodeVariableReferenceExpression("sortField"));
            fetchByInvoke2.Parameters.Add(new CodeVariableReferenceExpression("order"));
            fetchByInvoke2.Parameters.Add(new CodeDirectionExpression(FieldDirection.Out, totalReference));

            CodeAssignStatement assignTotal = new CodeAssignStatement(
                new CodeVariableReferenceExpression("totalEntities"),
                totalReference);

            CodeMethodReturnStatement returnStatement2 = new CodeMethodReturnStatement(
                (useInfo ?
                (CodeExpression)new CodeMethodInvokeExpression(collectionReference2, "ToInfoArray") :
                (CodeExpression)collectionReference2));

            fetchByRelationship2.Statements.Add(totalDeclaration);
            fetchByRelationship2.Statements.Add(collectionDeclaration2);
            fetchByRelationship2.Statements.Add(assignTotal);
            fetchByRelationship2.Statements.Add(returnStatement2);
            #endregion

            return methods.ToArray();
        }
        private CodeMemberMethod[] CreateFetchBySiblings(EntityRelationship relationship)
        {
            List<CodeMemberMethod> methods = new List<CodeMemberMethod>();
            bool useInfo = (Dependency.GetContract<IInfoObject>(relationship.From) != null);

            #region Create fetchByRelationship
            CodeMemberMethod fetchByRelationship = new CodeMemberMethod();
            methods.Add(fetchByRelationship);
            fetchByRelationship.Attributes = MemberAttributes.Public;
            fetchByRelationship.Name = relationship.From.Name + "CollectionFetchBy" + relationship.With.Name;
            fetchByRelationship.ReturnType = (useInfo ?
                new CodeTypeReference(relationship.From.Name + "Info") :
                new CodeTypeReference(relationship.From.Name));

            CodeTypeReference collectionTypeReference = new CodeTypeReference(relationship.From.Name + "Collection");
            CodeTypeReferenceExpression collectionTypeReferenceExpression = new CodeTypeReferenceExpression(relationship.From.Name + "Collection");
            CodeMethodInvokeExpression fetchByInvoke = new CodeMethodInvokeExpression(
                collectionTypeReferenceExpression,
                "Fetch");

            CodeVariableDeclarationStatement collectionDeclaration1 = new CodeVariableDeclarationStatement(
                collectionTypeReference,
                "collection",
                fetchByInvoke);
            CodeVariableReferenceExpression collectionReference1 = new CodeVariableReferenceExpression("collection");

            CodeMethodReturnStatement returnStatement1 = new CodeMethodReturnStatement(
                (useInfo ?
                (CodeExpression)new CodeMethodInvokeExpression(collectionReference1, "ToInfoArray") :
                (CodeExpression)collectionReference1));

            foreach (EntityField field in relationship.With.Fields)
            {
                if (field.IsId)
                {
                    fetchByRelationship.Parameters.Add(new CodeParameterDeclarationExpression(
                        new CodeTypeReference(field.Type),
                        StringUtil.ToCamelCase(field.Name)));

                    fetchByInvoke.Parameters.Add(new CodeVariableReferenceExpression(StringUtil.ToCamelCase(field.Name)));
                }
            }

            fetchByRelationship.Statements.Add(collectionDeclaration1);
            fetchByRelationship.Statements.Add(returnStatement1);
            #endregion

            return methods.ToArray();
        }
        #endregion

        #region Create CRUD
        private CodeMemberMethod[] GetCrudMethods(Entity entity)
        {
            List<CodeMemberMethod> methods = new List<CodeMemberMethod>();

            methods.Add(CreateFetch(entity));
            methods.Add(CreateInsert(entity));
            methods.Add(CreateUpdate(entity));
            methods.Add(CreateDelete(entity));

            return methods.ToArray();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        /// <example>
        /// [WebMethod]
        /// public void CustomerDelete(int customerId)
        /// {
        ///     // if IInfoObject dependency found
        ///     CustomerInfo info = new CustomerInfo();
        ///     info.CustomerId = customerId;
        ///     Customer obj = Customer.Load(info);
        /// 
        ///     // if IInfoObject dependency not found
        ///     //Customer obj = Customer.Fetch(customerId);
        /// 
        ///     obj.Delete(true);
        /// }
        /// </example>
        private CodeMemberMethod CreateDelete(Entity entity)
        {
            bool useInfo = (Dependency.GetContract<IInfoObject>(entity) != null);

            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = entity.Name + "Delete";
            method.Attributes = MemberAttributes.Public;
            method.CustomAttributes.Add(new CodeAttributeDeclaration("WebMethod"));

            CodeVariableReferenceExpression objReference = new CodeVariableReferenceExpression("obj");
            CodeMethodInvokeExpression deleteInvoke = new CodeMethodInvokeExpression(
                objReference,
                "Delete",
                new CodePrimitiveExpression(true));
            CodeTypeReference entityTypeReference = new CodeTypeReference(
                entity.Name);
            CodeTypeReferenceExpression entityTypeReferenceExpression = new CodeTypeReferenceExpression(
                entity.Name);

            if (useInfo)
            {
                CodeVariableDeclarationStatement infoDeclaration = new CodeVariableDeclarationStatement(
                    entity.Name + "Info",
                    "info",
                    new CodeObjectCreateExpression(entity.Name + "Info"));
                CodeVariableReferenceExpression infoReference = new CodeVariableReferenceExpression("info");
                method.Statements.Add(infoDeclaration);

                foreach (EntityField field in entity.Fields)
                {
                    if (field.IsId)
                    {
                        method.Parameters.Add(new CodeParameterDeclarationExpression(
                            field.Type,
                            StringUtil.ToCamelCase(field.Name)));
                        method.Statements.Add(new CodeAssignStatement(
                            new CodePropertyReferenceExpression(
                                infoReference,
                                field.Name),
                            new CodeVariableReferenceExpression(StringUtil.ToCamelCase(field.Name))));
                    }
                }
                CodeMethodInvokeExpression  loadInvoke = new CodeMethodInvokeExpression(
                    entityTypeReferenceExpression,
                    "Load",
                    infoReference);
                CodeVariableDeclarationStatement objDeclaration = new CodeVariableDeclarationStatement(
                    entity.Name,
                    "obj",
                    loadInvoke);
                method.Statements.Add(objDeclaration);
            }
            else
            {
                CodeMethodInvokeExpression fetchInvoke = new CodeMethodInvokeExpression(
                        entityTypeReferenceExpression,
                        "Fetch");
                CodeVariableDeclarationStatement objDeclaration = new CodeVariableDeclarationStatement(
                    entityTypeReference,
                    "obj",
                    fetchInvoke);
                method.Statements.Add(objDeclaration);

                foreach (EntityField field in entity.Fields)
                {
                    if (field.IsId)
                    {
                        method.Parameters.Add(new CodeParameterDeclarationExpression(
                            field.Type,
                            StringUtil.ToCamelCase(field.Name)));
                        fetchInvoke.Parameters.Add(new CodeVariableReferenceExpression(StringUtil.ToCamelCase(field.Name)));
                    }
                }
            }
            method.Statements.Add(deleteInvoke);

            return method;
        }

        private CodeMemberMethod CreateUpdate(Entity entity)
        {
            bool useInfo = (Dependency.GetContract<IInfoObject>(entity) != null);

            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = entity.Name + "Update";
            method.Attributes = MemberAttributes.Public;
            method.CustomAttributes.Add(new CodeAttributeDeclaration("WebMethod"));

            CodeVariableReferenceExpression objReference = new CodeVariableReferenceExpression("obj");
            CodeMethodInvokeExpression saveInvoke = new CodeMethodInvokeExpression(
                objReference,
                "Save");
            CodeTypeReference entityTypeReference = new CodeTypeReference(
                entity.Name);
            CodeTypeReferenceExpression entityTypeReferenceExpression = new CodeTypeReferenceExpression(
                entity.Name);

            if (useInfo)
            {
                CodeVariableReferenceExpression infoReference = new CodeVariableReferenceExpression("info");
                
                method.Parameters.Add(new CodeParameterDeclarationExpression(
                    entity.Name + "Info",
                    "info"));             
                CodeMethodInvokeExpression loadInvoke = new CodeMethodInvokeExpression(
                    entityTypeReferenceExpression,
                    "Load",
                    infoReference);
                CodeVariableDeclarationStatement objDeclaration = new CodeVariableDeclarationStatement(
                    entity.Name,
                    "obj",
                    loadInvoke);
                method.Statements.Add(objDeclaration);
                foreach (EntityField field in entity.Fields)
                {
                    method.Parameters.Add(new CodeParameterDeclarationExpression(
                       field.Type,
                       StringUtil.ToCamelCase(field.Name)));
                    method.Statements.Add(new CodeAssignStatement(
                        new CodePropertyReferenceExpression(
                            infoReference,
                            field.Name),
                        new CodeVariableReferenceExpression(StringUtil.ToCamelCase(field.Name))));
                }
            }
            else
            {
                CodeMethodInvokeExpression fetchInvoke = new CodeMethodInvokeExpression(
                        entityTypeReferenceExpression,
                        "Fetch");
                CodeVariableDeclarationStatement objDeclaration = new CodeVariableDeclarationStatement(
                    entityTypeReference,
                    "obj",
                    fetchInvoke);
                method.Statements.Add(objDeclaration);

                foreach (EntityField field in entity.Fields)
                {
                    method.Parameters.Add(new CodeParameterDeclarationExpression(
                       field.Type,
                       StringUtil.ToCamelCase(field.Name)));
                    if (field.IsId)
                    {
                        fetchInvoke.Parameters.Add(new CodeVariableReferenceExpression(StringUtil.ToCamelCase(field.Name)));
                    }
                    else
                    {
                        method.Statements.Add(new CodeAssignStatement(
                            new CodePropertyReferenceExpression(
                                objReference,
                                field.Name),
                            new CodeVariableReferenceExpression(StringUtil.ToCamelCase(field.Name))));
                    }
                }
            }
            method.Statements.Add(saveInvoke);

            return method;
        }

        private CodeMemberMethod CreateInsert(Entity entity)
        {
            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = entity.Name + "Insert";
            method.Attributes = MemberAttributes.Public;
            method.CustomAttributes.Add(new CodeAttributeDeclaration("WebMethod"));

            CodeVariableReferenceExpression objReference = new CodeVariableReferenceExpression("obj");
            CodeMethodInvokeExpression saveInvoke = new CodeMethodInvokeExpression(
                objReference,
                "Save");
            CodeTypeReference entityTypeReference = new CodeTypeReference(
                entity.Name);
            CodeTypeReferenceExpression entityTypeReferenceExpression = new CodeTypeReferenceExpression(
                entity.Name);

            CodeMethodInvokeExpression createNewInvoke = new CodeMethodInvokeExpression(
                        entityTypeReferenceExpression,
                        "CreateNew");
            CodeVariableDeclarationStatement objDeclaration = new CodeVariableDeclarationStatement(
                entityTypeReference,
                "obj",
                createNewInvoke);

            method.Statements.Add(objDeclaration);
            foreach (EntityField field in entity.Fields)
            {
                if (!field.IsAuto)
                {
                    method.Parameters.Add(new CodeParameterDeclarationExpression(
                        field.Type,
                        StringUtil.ToCamelCase(field.Name)));
                    method.Statements.Add(new CodeAssignStatement(
                        new CodePropertyReferenceExpression(
                            objReference,
                            field.Name),
                        new CodeVariableReferenceExpression(StringUtil.ToCamelCase(field.Name))));
                }
            }
            method.Statements.Add(saveInvoke);

            return method;
        }

        private CodeMemberMethod CreateFetch(Entity entity)
        {
            bool useInfo = (Dependency.GetContract<IInfoObject>(entity) != null);

            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = entity.Name + "Fetch";
            method.Attributes = MemberAttributes.Public;
            method.CustomAttributes.Add(new CodeAttributeDeclaration("WebMethod"));
            method.ReturnType = (useInfo ?
                new CodeTypeReference(entity.Name + "Info") :
                new CodeTypeReference(entity.Name));

            CodeTypeReferenceExpression entityTypeReference = new CodeTypeReferenceExpression(entity.Name);
            CodeMethodInvokeExpression fetchInvoke = new CodeMethodInvokeExpression(
                entityTypeReference,
                "Fetch");
                        
            foreach (EntityField field in entity.Fields)
            {
                if (field.IsId)
                {
                    method.Parameters.Add(new CodeParameterDeclarationExpression(
                        field.Type,
                        StringUtil.ToCamelCase(field.Name)));
                    fetchInvoke.Parameters.Add(new CodeVariableReferenceExpression(StringUtil.ToCamelCase(field.Name)));
                }
            }

            fetchInvoke = (useInfo ?
                new CodeMethodInvokeExpression(
                     fetchInvoke,
                     "GetInfoObject") :
                fetchInvoke);

            method.Statements.Add(new CodeMethodReturnStatement(fetchInvoke));

            return method;
        } 
        #endregion

        #region Create Master Declaration
        /// <summary>
        /// Creates the base partial implementation including inheriting from the base class as well
        /// as applying the WebService and WebServiceBinding attributes.
        /// </summary>
        /// <param name="family"></param>
        /// <returns></returns>
        protected virtual CodeTypeDeclaration CreateMasterDeclaration(string family)
        {
            CodeTypeDeclaration d = new CodeTypeDeclaration();
            d.Name = "Service";
            d.IsClass = true;
            d.IsPartial = true;
            d.Attributes = MemberAttributes.Public;
            d.BaseTypes.Add(new CodeTypeReference("System.Web.Services.WebService"));

            d.CustomAttributes.Add(new CodeAttributeDeclaration(
                "WebService",
                new CodeAttributeArgument("Namespace", new CodePrimitiveExpression("http://" + family + "/Service/"))));

            d.CustomAttributes.Add(new CodeAttributeDeclaration(
                "WebServiceBinding",
                new CodeAttributeArgument("ConformsTo", new CodeSnippetExpression("WsiProfiles.BasicProfile1_1"))));

            return d;
        } 
        #endregion

        #region Create Main Class
        /// <summary>
        /// Creates a basic partial class definition.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        protected virtual CodeTypeDeclaration CreateMainClass(Entity entity)
        {
            CodeTypeDeclaration d = new CodeTypeDeclaration();
            d.Name = "Service";
            d.IsClass = true;
            d.IsPartial = true;
            d.Attributes = MemberAttributes.Public;

            return d;
        }
        #endregion
    }
}
